(function(global){

  // 一些常量
  const DEGREES_PER_RADIAN = 180.0 / Math.PI;
  const RADIANS_PER_DEGREE = Math.PI / 180.0;
  const TWO_PI = 2.0 * Math.PI;
  const MAX_RADIUS = 6378137;
  const WIDTH = 2.0 * Math.PI * MAX_RADIUS;
  const HEIGHT = WIDTH;
  const numberOfLevelZeroTilesX = 1;
  const numberOfLevelZeroTilesY = 1;
  const tileWidth = 256;
  const tileHeight = 256;
  const EPSILON15 = 0.000000000000001;
  const boundary = {
    west: -Math.PI,
    south: -1.4844222297453322,
    east: Math.PI,
    north: 1.4844222297453322,
    width: 2.0 * Math.PI,
    height: 1.4844222297453322 * 2
  };

  // 获取级别横向瓦片数量
  // 来自 cesium
  function getNumberOfXTilesAtLevel(level) {
    return numberOfLevelZeroTilesX << level;
  }

  // 获取级别纵向瓦片数量
  // 来自 cesium
  function getNumberOfYTilesAtLevel(level) {
    return numberOfLevelZeroTilesY << level;
  }

  // 0 级的时候像素间距
  const levelZeroTexelSpacing = WIDTH / (tileWidth * getNumberOfXTilesAtLevel(0));
  
  // 瓦片转经纬度
  // 来自 cesium
  function tileXYToRadians(x, y, level) {
    const rectangle = boundary;
    const xTiles = getNumberOfXTilesAtLevel(level);
    const yTiles = getNumberOfYTilesAtLevel(level);

    const xTileWidth = rectangle.width / xTiles;
    const west = x * xTileWidth + rectangle.west;
    const east = (x + 1) * xTileWidth + rectangle.west;

    const yTileHeight = rectangle.height / yTiles;
    const north = rectangle.north - y * yTileHeight;
    const south = rectangle.north - (y + 1) * yTileHeight;

    return {
      west,
      south,
      east,
      north
    };
  }

  // 经纬度转瓦片
  // 来自 cesium
  function radiansToTileXY(longitudeRadians, latitudeRadians, level) {
    const rectangle = boundary;
    const xTiles = getNumberOfXTilesAtLevel(level);
    const yTiles = getNumberOfYTilesAtLevel(level);

    const xTileWidth = rectangle.width / xTiles;
    const yTileHeight = rectangle.height / yTiles;

    let xTileCoordinate = (longitudeRadians - rectangle.west) / xTileWidth | 0;

    if (xTileCoordinate >= xTiles) {
      xTileCoordinate = xTiles - 1;
    }

    let yTileCoordinate = (rectangle.north - latitudeRadians) / yTileHeight | 0;

    if (yTileCoordinate >= yTiles) {
      yTileCoordinate = yTiles - 1;
    }

    return {
      x: xTileCoordinate,
      y: yTileCoordinate
    };
  }

  /**
   * 仅计算 y 方向的像素间距
   * TODO 同时计算 x y 方向的像素间距 取最小值
   * camera { yfov, position }
   * screen [width, height]
   * pos [x, y, z] 射线于平面交点
   */
  function getCurrentTexelSpacing(camera, screen, pos) {
    const distanceToPos = glMatrix.vec3.distance(camera.position, pos);

    return (2.0 * Math.tan(camera.yfov / 2.0) * distanceToPos) / screen[1];
  }

  // 根据像素间距计算当前层级
  function getLevel(texelSpacing) {
    const twoToTheLevelPower = levelZeroTexelSpacing / texelSpacing;
    const level = Math.log(twoToTheLevelPower) / Math.log(2);
    const rounded = Math.round(level);

    return Math.max(rounded | 0, 0);
  }

  function getCurrentLevel(camera, screen, pos) {
    const texelSpacing = getCurrentTexelSpacing(camera, screen, pos);

    return getLevel(texelSpacing);
  }

  function lonlat2lonlatRadians(lonlat) {
    return [
      lonlat[0] * RADIANS_PER_DEGREE,
      lonlat[1] * RADIANS_PER_DEGREE
    ];
  }

  function lonlatRadians2lonlat(lonlat) {
    return [
      lonlat[0] * DEGREES_PER_RADIAN,
      lonlat[1] * DEGREES_PER_RADIAN
    ];
  }

  /**
   * world [x, z]
   * center [longitudeRadians, latitudeRadians]
   * @returns lnglat [longitudeRadians, latitudeRadians]
   */
  function tileWorld2lonlatRadians(world /* , center */) {
    const xRadians = world[0] / MAX_RADIUS;
    const yRadians = (world[1] / HEIGHT) * boundary.height;

    return [xRadians, -yRadians];
  }

  function tileLonlatRadians2world(lonlat) {
    const x = ((lonlat[0]) / boundary.width) * WIDTH;
    const y = ((-lonlat[1]) / boundary.height) * HEIGHT;

    return [x, y];
  }

  // 中心点是 0， 0
  function lonlat2world(lonlat) {
    const lonlatRadians = lonlat2lonlatRadians(lonlat);
    const lonlatX = lonlatRadians[0] * MAX_RADIUS;
    const lonlatY = -Math.log(Math.tan(Math.PI / 4.0 + lonlatRadians[1] / 2.0)) * MAX_RADIUS;

    return [lonlatX, lonlatY];
  }

  // 中心点是 0， 0
  function world2lonlat(world) {
    const xRadians = world[0] / MAX_RADIUS;
    const yRadians = 2.0 * (1.0 / Math.tan(Math.pow(Math.exp(1), world[1] / MAX_RADIUS))) - Math.PI / 2.0;

    return lonlatRadians2lonlat([xRadians, -yRadians]);
  }

  /**
   * @param {*} ray [origin, direction]
   * @param {*} plane [normal, distance]
   */
  function rayIntersectionWidthPlane(ray, plane) {
    const origin = ray[0];
    const direction = ray[1];
    const normal = plane[0];
    const distance = plane[1];
    const denominator = glMatrix.vec3.dot(normal, direction);

    if (Math.abs(denominator) < EPSILON15) {
      // 平行的情况
      return undefined;
    }

    const t = (-distance - glMatrix.vec3.dot(normal, origin)) / denominator;

    if (t < 0) {
      return undefined;
    }

    const result = glMatrix.vec3.create();

    glMatrix.vec3.scale(result, direction, t);
    glMatrix.vec3.add(result, origin, result);

    return result;
  }

  global.rayIntersectionWidthPlane = rayIntersectionWidthPlane;
  global.getCurrentLevel = getCurrentLevel;
  global.tileWorld2lonlatRadians = tileWorld2lonlatRadians;
  global.radiansToTileXY = radiansToTileXY;
  global.tileXYToRadians = tileXYToRadians;
  global.getNumberOfXTilesAtLevel = getNumberOfXTilesAtLevel;
  global.getNumberOfYTilesAtLevel = getNumberOfYTilesAtLevel;
  global.tileLonlatRadians2world = tileLonlatRadians2world;

})(window);